-
Notifications
You must be signed in to change notification settings - Fork 2k
Call completeStream only once on failure. #13814
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: jetty-12.0.x
Are you sure you want to change the base?
Conversation
a8cf31d to
bee65a6
Compare
Fix #13470 by calling completeStream only once even when there is a failure in the channel callback.
jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java
Outdated
Show resolved
Hide resolved
|
The issue is fundamentally that For this to happen, we need a failure to be detected in the Lines 1573 to 1580 in 8dbd986
This means that We can see that there is a failure detected in
These are all plausible application errors, especially with something like server sent events. So once thread 258 has called which is called from this code after the handler has been invoked: Lines 691 to 713 in 8dbd986
Note that in order for this code to actually call Thus I believe the core fix is to not call Unfortunately I have been unable to produce a unit test for this, as I believe it needs precisely unlucky timing and an application error. |
Fix #13470 by calling completeStream only once even when there is a failure in the channel callback.
|
@sbordet @lorban Can you review the diagnosis that @janbartel and I have come up with. I'm 90% sure this is it, but I cannot reproduce (any thoughts how we might be able to do that?). |
|
Note that we added this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ChannelCallback.failed() seems not entirely correct either.
Can we write test cases for this scenario?
jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java
Outdated
Show resolved
Hide resolved
| // We are committed and still handling, so let the HandlerInvoker complete, ignoring any pending reads/writes. | ||
| httpChannelState._streamSendState = StreamSendState.LAST_COMPLETE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, not sure we should ignore pending writes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or pending reads.... it is a difficult one. I'll at least change the code to enumerate the possible states
jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java
Outdated
Show resolved
Hide resolved
@sbordet I will look....
Very hard, because unless there is another thread racing the second completeStream is not called. I'm open to suggestions. |
Fix #13470 by calling completeStream only once even when there is a failure in the channel callback. refactor success and failure into single method.
Fix #13470 by calling completeStream only once even when there is a failure in the channel callback. refactor success and failure into single method. Improved EventSourceServlet
Fix #13470 by calling completeStream only once even when there is a failure in the channel callback. refactor success and failure into single method. Improved EventSourceServlet
|
@sbordet @lorban I'm getting concerned at the number of tests this PR is breaking in its current state. So I propose that this PR should simply be 83c1718 for 12.0.x (perhaps with the EventSourceServlet cleanups), and then we can do a wider cleanup and refactor in 12.1.x next month. |
Fix #13470 by calling completeStream only once even when there is a failure in the channel callback. refactor success and failure into single method. Improved EventSourceServlet
Fix #13470 by calling completeStream only once even when there is a failure in the channel callback. refactor success and failure into single method. Improved EventSourceServlet
Fix #13470 by calling completeStream only once even when there is a failure in the channel callback. refactor success and failure into single method. Improved EventSourceServlet
|
Regarding the minimal 12.0 fix, I think |
| Throwable unconsumed = stream.consumeAvailable(); | ||
| if (failure != null) | ||
| ExceptionUtil.addSuppressedIfNotAssociated(failure, unconsumed); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just ExceptionUtil.combine(failure, stream.consumeAvailable()); as the line above?
But then, this else block is identical to the else-if above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No this is different. If failure==null, then it remains null. i.e. it is not a failure to not consume. I'll comment....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I will try without the whole branch....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Branch is needed
jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ExceptionUtil.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java
Show resolved
Hide resolved
jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java
Outdated
Show resolved
Hide resolved
| private class HandlerInvoker implements Invocable.Task, Callback | ||
| // HandlerInvoker is used as the Response's _writeCallback when ChannelCallback is succeeded and the last send still | ||
| // needs to be done, i.e.: _streamSendState set to LAST_SENDING by lockedLastStreamSend(). | ||
| private class HandlerInvoker implements Task, Callback |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should split the functionality of this class.
Leave run() in HandlerInvoker, but move the Callback functionality into a LastStreamSendCallback class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really not a 12.0 thing then
| failedCallback = response._writeCallback; | ||
| response._writeCallback = httpChannelState._handlerInvoker; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this way, we may wait forever for the write to complete and invoke the _handlerInvoker.
How about this:
| failedCallback = response._writeCallback; | |
| response._writeCallback = httpChannelState._handlerInvoker; | |
| Runnable task = response.lockedFailWrite(failure); | |
| failedCallback = Callback.from(task, httpChannelState._handlerInvoker); |
In 12.1.x we will leverage the new cancelSend() feature automatically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that continuing on with a pending write pointing at the HttpChannelState is a good idea. Feels like an invitation for a race. What is wrong waiting for the write to complete. It won't be forever unless they have disabled idle timeout.
jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java
Show resolved
Hide resolved
| { | ||
| assert _callbackCompleted; | ||
| _streamSendState = StreamSendState.LAST_COMPLETE; | ||
| completeStream = _handling == null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| completeStream = _handled; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried, but we also need to complete a stream in cases where we have not started handling yet.
jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java
Outdated
Show resolved
Hide resolved
…470-completeStreamOnce
Fix #13470 by calling completeStream only once even when there is a failure in the channel callback.